/***************************************************************************
TARInspector 1.0 (a .tar file content inspector module for the NeXTSTEP Workspace Manager)
Copyright (C) 1993	Glenn Brown

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version.

This program is distributed WITHOUT ANY WARRANTY; without even the implied warrenty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***************************************************************************/

/*Note:  Don't be intimidated by the size of this file.  It is so big because is has copious amounts of comments
	You should read GeneralRef/19_WorkspaceManager/Classes/WMInspector.rtf before trying to understand this code.  It's really simple, though:  The workspace manager simply calls the ok: method whenever the user presses the ok ('Unarchive') button and calls revert:  whenever the revert ('List Contents') button is pressed -OR- the workspace manager wants the inspector to update its display.  The new method is called the first time TARInspector is used during a login session.*/

#import <mach/cthreads.h>
#import <stdio.h>
#import <mach/cthreads.h>
#import "TARInspector.h"

@implementation TARInspector

static id tarInspector = nil;
//	I hate globals, but its in the 
//	GeneralRef/19_WorkspaceManager/Classes/WMInspector.rtf example and
//	I say:  if it ain't broke...

/***********
* new:  This routine is based on the new routine found in 
* the online developer documentation modified as noted.  See 
* GeneralRef/19_WorkspaceManager/Classes/WMInspector.rtf
************/
+ new
{    if (tarInspector == nil) {
        char path[MAXPATHLEN+1];
        NXBundle *bundle = [NXBundle bundleForClass:self];
      
        self = tarInspector = [super new];
        if ([bundle getPath:path 
                forResource:"TARInspector"
                ofType:"nib"]) {
            [NXApp loadNibFile:path owner:tarInspector];
			
			//vvvvvvvvvvvvvvvvvvvvvvvvGlenn's modifications
			instructions = NXOpenMemory(NULL,0,NX_READWRITE);
			[textOutput writeRichText:instructions];
			//	the above two lines open a stream in memory and write the
			//	rtf format TARInspector instructions just loaded from the
			//	.nib file into it so the can be reloaded into the 
			//	scroller later.
			//^^^^^^^^^^^^^^^^^^^^^^^^
			
		} else {
            fprintf (stderr, "Couldn't load TARInspector.nib\n");
            tarInspector = nil;
        }
    }
    return tarInspector;
}

/************
* asynchronouslyPerformFileOperation: This is a function rather than
* a method so that it can be executed asynchronously.  See where it is
* called for details.  Also see
* GeneralReference/02_ApplicationKit/Protocols/NXWorkspaceRequestProtocol.rtf
*************/
int	asynchronouslyPerformFileOperation(char *sels){
	int err;
	
	err = [[Application workspace] 
	performFileOperation: WSM_DECOMPRESS_OPERATION
	source: ""
	destination:""
	files: sels
	options:""];
	//	Decompresses the selections using the workspace's decompress
	//	feature.  The workspace does nice things during decompression like
	//	putting a message in the file viewer window and showing data about
	//	the decompression operation in the processes window.
	//		Unfortunately, the 'Unarchive' feature of the workspace manager is
	//	either not documented or does not exist as a 'File Operation'.
	//	Probably, the inspector that comes with 3.0 does not use the
	//	performFileOperation:...: method to do unarchiving but, rather, does
	//	everything (messages, e.g.) directly.  I could not do this, however,
	//	since the internals of the Workspace manager are not documented.
				
	free(sels);
	return err;	
}

/*************
* updateButtons:  updates the buttons as recorded in the objects
* instance variables okEnabled and revertEnabled.
**************/
- updateButtons{
	[[[self okButton] setEnabled:okEnabled] setTitle:"Unarchive"];
	[[[self revertButton] setEnabled:revertEnabled] setTitle:"ListContents"];
	return self;
}

/************
* ok: This method is called by the workspace when the OK ("Unarchive") button
* is pressed. 
* See GeneralRef/19_WorkspaceManager/Classes/WMInspector.rtf 
*************/
- ok:sender{
	unsigned numSels;
	char *sels;
	
	okEnabled = NO;
	[self updateButtons]; 
	//	deactivate the "Unarchive" button so the user will not unarchive
	//	the file twice.
	numSels = [self selectionCount]; 
	//	find out how many files are selected and ...
	sels = calloc(numSels*(MAXPATHLEN+1),sizeof(char));
	//	allocate enough memory to hold the numSels selections plus the
	//  seperators between them plus the null termination.
	if(sels){
	//	if memory allocation was successful
		[self selectionPathsInto:sels separator:'\t'];
		//	read the names of the selected files into the allocated space
		//  using the seperator that the WorkspaceRequestProtocol asks for.
		cthread_detach(cthread_fork(
		(cthread_fn_t)asynchronouslyPerformFileOperation,
		(any_t)sels));
		//	Tells the workspace manager to decompress the file.
		//	This call requires a little explaining!
		//		Because this inspector module executes as part of the
		//	workspace manager's main thread of execution, and because the
		//	performFileOperation:..: method blocks until workspace manager
		//	responds to the workspace request, the workspace manager will
		//  hang unless a seperate thread makes the workspaceManagerRequest.
		//		At least that's the only explaination I could come up with
		//	for why the workspace manager would hand during the request
		//	before I put the request in another thread!
	}else{
		//	Insert error messager here if you want to.  It will be called
		//	if memory for the selection could not be allocated.
		return nil;
	}
    [super ok:sender];	
    return self;
	//	Above 2 lines as spec'd in 
	//	GeneralRef/19_WorkspaceManager/Classes/WMInspector.rtf
}


/************
* listContentsOfTarFile:  This method just calls 
* 'tar -tf /selection/' and pipes the result into the Text object in
* the ScrollView.  Assumes there is only one selection.
*************/
- listContentsOfTarFile:sender{
	char command[13+MAXPATHLEN+1]; 
	//	space for the command line to send to 'sh'
	//  13 for "/bin/tar -tf ", MAXPATHLEN for file path, 1 for null at end
	FILE *result;	//file (stream) holding text result of 'tar -tf command'
	NXStream *nxf;	//will point to same stream as result.
	strcpy(command,"/bin/tar -tf ");	
	//	copy the command to the command line
	[self selectionPathsInto:&command[13] separator:'\0'];
	//	puts the pathname of the selected file at the end of the command line.
	[[textOutput setMonoFont:YES] setText:""];
	//	erase text in inspector and set mode to unformatted text
	[[textOutput selectAll:self] alignSelLeft:self];
	[textOutput setSelProp:NX_INDENT to:30];
	[[textOutput setSelFontSize:10.0] setSelFontStyle:0];
	//	3 lines above set text display attributes.
	[[textOutput selectNull] sizeToFit];	//	update display
	result = popen(command,"r");		
	//execute the command and store results in a stream called result.
	if(result != NULL){					//if tar -tf success then
		nxf=NXOpenFile(fileno(result),NX_READONLY);
		//	converts (FILE *)result to a (NXStream *)
		if(nxf!=NULL){					//if nothing weird happened
			[textOutput readText:nxf];	//put result in text field
 //			[textOutput sizeToFit];		//update display
			NXClose(nxf);				//close the stream
		}else{
			//If there is an error here, I want to know about it!
			//I've never seen this message, however.
			[textOutput setText:"Error (1) listing contents."];
		}
		pclose(result);					
		//	closes the file (stream) that the result were read into.
		//	I'm not sure that I need to close it, since NXClose was called
		//	above, but in the case that nxf == null, NXClose will not be
		//	called and the file should be closed.  Also, the pclose man
		//	page sez that pclose can tell is its arg is not something that
		//	needs to be closed with pclose.
		
	}else [textOutput setText:"Error (2) listing contents."];
	//I would be amazed if this error were reported.
	[textOutput sizeToFit];	//	make all text visible.
	[super revert:sender];	
	//	as spec'd in GeneralRef/19_WorkspaceManager/Classes/WMInspector.rtf
	return self;
}

/************
** isNewSelection
** Tests if a new selection has been made.  If so, it records the new
** selection as LastSelection along with lastSelection count.
** This method is only necessary because NeXT doesn't tell us this info
** directly... [Lame!]
**************/
- (int) isNewSelection{
	char *currentSelection;
	int currentSelectionCount;
	currentSelectionCount = [self selectionCount]; //read current # selected
	currentSelection = calloc(					//alloc space for the
		currentSelectionCount*(MAXPATHLEN+1),	//tab delimited selection
		sizeof(char));							//list
	if(currentSelection){							//if alloc succeeded
		[self selectionPathsInto:currentSelection separator: '\t'];
												//read path into alloc'ed space
		if(lastSelection==NULL){	
			//if this is first time to read a selection, then the current
			//selection, whatever it is, is new.
			lastSelection=currentSelection;		//record the current selection
			lastSelectionCount=currentSelectionCount;
			return YES;							//and report that it was new
		}else if(strcmp(currentSelection,lastSelection)){
				//if current and prev sels are different...
				//(note: strcmp will _not_ be called if cS or lS is null
			lastSelectionCount = currentSelectionCount; //then update count
			free(lastSelection); 				//free the old sel info
			lastSelection = currentSelection;	//update selection
			return YES;							//report that sel is new
		}else{								//if current selection is not new
			free(currentSelection);			//free it.
		}
	}/*else{Here would be a good place for an error message}*/
	return NO;	//returned if current selection could not be alloc'd
				//or current selection is not new
}

/*************
** updateButtonsForNewSelection:  If there is a new selection, enable
** the appropriate buttons
**************/
- updateButtonsForNewSelection{
	okEnabled = YES;							//enable 'Unarchive' button
	revertEnabled = ([self selectionCount]==1);
	//	enable 'List Conents' button iff one file is selected
	[self updateButtons];						//update the buttons.
	return self;
}

/*************
** displayInstructions: puts the instructions (which were read earlier
** from the Text object just after they were loaded from the .nib file)
** back in the text display
**************/
- displayInstructions{
	[textOutput setText:""];		//force Text object to scroll to top
	NXSeek(instructions, 0L, NX_FROMSTART);
	//	rewind the stream holding the instructions to the start.
	[[textOutput setMonoFont:NO] readRichText:instructions];
	//  enable RTF mode and load the RTF instructions into the display
	return self;
}

/*************
** revert:  This is the heart of the inspector.  This method is called if
** the workspace manager wants the inspector to be updated or the revert
** button is pressed.
** See GeneralRef/19_WorkspaceManager/Classes/WMInspector.rtf.
**************/
- revert:sender{
	if([self revertButton] != sender){
		//	This code is executed iff the revert button was _not_ the cause.
		//	of the revert message being sent... Meaning the workspace
		//	manager wants a display update.
		if([self isNewSelection]){
		//	if a new selection has been made
			[self updateButtonsForNewSelection];
			//	update buttons as appropriate.
			[self displayInstructions];
			//	erase old content listing by replacing with instructions
		} else [self updateButtons];
	}else if([self selectionCount]==1){	
		//	This branch is taken only if the user actually pressed
		//	the button and only one file is selected.
		revertEnabled = NO;
		[self updateButtons];
		//	above two lines disable the 'List Contents' button so the
		//	user cannot waste cycles listing the contents more than once!
		[self listContentsOfTarFile:sender];  //listContentsOf the selected TarFile
	}
	[super revert:sender];		
	//	as specified in GeneralRef/19_WorkspaceManager/Classes/WMInspector.rtf
    return self;
}

@end
